Skip to content

feat: GGX Microfacet BRDF — physically-based metallic & specular rendering#9

Merged
Cle2ment merged 12 commits into
masterfrom
feat/ggx-microfacet-brdf
Jun 4, 2026
Merged

feat: GGX Microfacet BRDF — physically-based metallic & specular rendering#9
Cle2ment merged 12 commits into
masterfrom
feat/ggx-microfacet-brdf

Conversation

@Cle2ment
Copy link
Copy Markdown
Owner

@Cle2ment Cle2ment commented Jun 4, 2026

Summary

Replaces pure Lambertian diffuse BRDF with GGX Microfacet BRDF across all three rendering paths. Roughness and Metallic material properties now actually affect rendering — smooth surfaces show specular highlights, metals reflect with color.

Changes (3 atomic commits)

Commit Path File
3e0db1d CUDA GPU CUDARenderer.cuh
075e2a8 C++ CPU Renderer.cpp
570fcf1 ISPC SIMD PathTracer.ispc

BRDF Components

  • Fresnel: Schlick approximation (F0 = mix(0.04, albedo, metallic))
  • Normal Distribution: Trowbridge-Reitz GGX
  • Geometry: Smith height-correlated (G = G1(wi) × G1(wo))
  • Sampling: VNDF importance sampling for specular lobe
  • Diffuse: Disney-style (kD = (1-F) × (1-metallic) × albedo / π)
  • MIS weight: Fresnel-based balance between specular and diffuse

Visual Impact

  • Roughness=0 → sharp mirror reflections
  • Roughness=1 → matte/diffuse (similar to before)
  • Metallic=1 → colored reflections (metal look)
  • Metallic=0 → white specular highlights (dielectric)

Files Changed

File +/−
CUDARenderer.cuh +151/−8
Renderer.cpp +76/−4
PathTracer.ispc +141/−15

Verification

  • ✅ All 3 commits independently buildable (xmake build -r)
  • ✅ NVCC + MSVC + ISPC compilation clean
  • ⚠️ Manual visual verification needed — run app and check:
    • Metallic slider affects reflection color
    • Roughness slider affects specular sharpness
    • Orange emissive sphere should still illuminate scene

Copilot AI review requested due to automatic review settings June 4, 2026 09:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the previous diffuse-only (Lambertian) bounce model with a GGX microfacet BRDF so that Roughness and Metallic materially affect shading in all three rendering backends (C++ CPU, ISPC SIMD, CUDA).

Changes:

  • Added GGX BRDF building blocks (Schlick Fresnel, GGX NDF/geometry, VNDF sampling) in each path.
  • Replaced the old “random hemisphere diffuse” bounce with GGX-based direction sampling + throughput update.
  • Introduced a spec/diff mixture PDF and BSDF evaluation intended to support metallic/specular behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 11 comments.

File Description
RayTracing/src/Renderer.cpp Adds GGX helpers and replaces CPU bounce logic with GGX evaluation/sampling.
RayTracing/src/PathTracer.ispc Adds GGX helpers and replaces ISPC bounce logic with GGX evaluation/sampling.
RayTracing/src/CUDARenderer.cuh Adds GGX helpers and replaces CUDA bounce logic with GGX evaluation/sampling in the device PerPixel loop.

Comment thread RayTracing/src/Renderer.cpp Outdated
Comment on lines +428 to +437
const float r1 = Utils::RandomFloat(seed), r2 = Utils::RandomFloat(seed);
const glm::vec3 H = Utils::SampleGGX_VNDF(w_o, a, r1, r2);
const float NdotH = glm::max(glm::dot(WorldNormal, H), 0.001f);
const glm::vec3 wi = glm::reflect(-w_o, H);
const float NdotL = glm::max(glm::dot(WorldNormal, wi), 0.001f);
const float NdotV = glm::max(glm::dot(WorldNormal, w_o), 0.001f);

const float D = Utils::GGX_D(NdotH, a);
const float G = Utils::GGX_G(NdotL, NdotV, a);
const glm::vec3 F = Utils::FresnelSchlick(NdotV, F0);
Comment on lines +422 to +426
// ── GGX Microfacet BRDF ──
const glm::vec3 w_o = -ray.Direction;
const glm::vec3 F0 = glm::mix(glm::vec3(0.04f), material.Albedo, material.Metallic);
const float rough = glm::max(material.Roughness, 0.001f);
const float a = rough * rough;
Comment thread RayTracing/src/Renderer.cpp Outdated
Comment on lines +443 to +451
const float specWeight = glm::clamp(glm::max(F.r, glm::max(F.g, F.b)), 0.05f, 0.95f);
const float specPdf = D * NdotH / (4.0f * NdotV + 0.001f);
const float diffPdf = NdotL / glm::pi<float>();
const float pdf = glm::max(specWeight * specPdf + (1.0f - specWeight) * diffPdf, 0.001f);

const glm::vec3 bsdf = (specBRDF + diffBRDF) * NdotL;
contribution *= bsdf / pdf;

ray.Direction = glm::normalize(wi);
Comment on lines 301 to +305
payload.WorldPosition.y + payload.WorldNormal.y * 0.0001f,
payload.WorldPosition.z + payload.WorldNormal.z * 0.0001f
);

// Cosine-weighted diffuse BRDF: sample direction in hemisphere via ONB
float3 u, v, w;
BuildONB(payload.WorldNormal, u, v, w);
float3 localDir = RandomCosineWeightedDirection(seed);
// ── GGX Microfacet BRDF ──
Comment thread RayTracing/src/CUDARenderer.cuh Outdated
Comment on lines +366 to +371
float specWeight = fmaxf(F.x, fmaxf(F.y, F.z));
specWeight = fminf(fmaxf(specWeight, 0.05f), 0.95f);

float specPdf = D * NdotH / (4.0f * NdotV + 0.001f);
float diffPdf = NdotL / 3.14159265358979323846f;
float pdf = fmaxf(specWeight * specPdf + (1.0f - specWeight) * diffPdf, 0.001f);
Comment on lines +303 to +307
varying float rn1 = RandomFloat(seed), rn2 = RandomFloat(seed);
varying float H[3];
SampleGGX_VNDF(woX, woY, woZ, a, rn1, rn2, H);
float NdotH = max(dot3(normX, normY, normZ, H[0], H[1], H[2]), 0.001f);

Comment thread RayTracing/src/PathTracer.ispc Outdated
Comment on lines +344 to +358
varying float specPdf = D * NdotH / (4.0f * NdotV + 0.001f);
varying float diffPdf = NdotL / 3.14159265358979323846f;
varying float pdf = max(specWeight * specPdf + (1.0f - specWeight) * diffPdf, 0.001f);

varying float bsdfR = (specR + diffR) * NdotL;
varying float bsdfG = (specG + diffG) * NdotL;
varying float bsdfB = (specB + diffB) * NdotL;

contribR *= bsdfR / pdf;
contribG *= bsdfG / pdf;
contribB *= bsdfB / pdf;

rDirX = wiX;
rDirY = wiY;
rDirZ = wiZ;
Comment on lines +323 to +327
float D = GGX_D(NdotH, a);
float G = GGX_G(NdotL, NdotV, a);
varying float F[3];
FresnelSchlick3(NdotV, F0, F);

Comment on lines +332 to +336
float3 localWi = make_float3(
2.0f * NdotH * localH.x - localWo.x,
2.0f * NdotH * localH.y - localWo.y,
2.0f * NdotH * localH.z - localWo.z
);
Comment thread RayTracing/src/PathTracer.ispc Outdated
Comment on lines +308 to +311
// Reflect outgoing around H
varying float wiX = 2.0f * NdotH * H[0] - woX;
varying float wiY = 2.0f * NdotH * H[1] - woY;
varying float wiZ = 2.0f * NdotH * H[2] - woZ;
@Cle2ment Cle2ment merged commit 05ea4b9 into master Jun 4, 2026
2 checks passed
@Cle2ment Cle2ment deleted the feat/ggx-microfacet-brdf branch June 5, 2026 02:31
@Cle2ment Cle2ment restored the feat/ggx-microfacet-brdf branch June 5, 2026 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants